package tst.xml.parent;

import scala.io.Source
import scalaxx.xml.{Document,Node, Elem}
import scalaxx.xml.parsing.ConstructingParser

// Q: Why not using scalaxx.xml.Node with the patched version of scalaxx.xml.Utility#hashCode?
// It will work as well and should be the default use.  But its slow as it evaluates on hashCode for each child as well.

object TestParent {
  
  def ROOT_NODE = scalaxx.xml.Utility.trim(ConstructingParser.fromSource(Source.fromString("<xml/>"), true).document.docElem).asInstanceOf[Elem]
   
  // straight node throws java.lang.UnsupportedOperationException: unsuitable as hash key
  // Pointer wraps it for that purpose
  case class Pointer[T](node: T ) {
    def apply(): T = node
    override def hashCode(): int = java.lang.System.identityHashCode(node) 
  }
     
  // alias for the actual base type
  type NodePointer = Pointer[Node]
  
  // make private
  def iCreateParentNodes(root: Node, factory: NodePointerFactory ): ParentMap =
    createParentNodes(root, ROOT_NODE, ParentMap(factory, new scala.collection.immutable.EmptyMap[NodePointer, Elem]()))
  
  abstract class NodePointerFactory {
    def createNodePointer(node: Node ): NodePointer
    def createParentNodes(root: Node): ParentMap
  }
  
  // IdentityNodePointer creates based on the identity of the Node
  object IdentityNodePointerFactory extends NodePointerFactory {
    def createNodePointer(node: Node) = Pointer[Node](node)
    def createParentNodes(root: Node): ParentMap = iCreateParentNodes(root, IdentityNodePointerFactory)
  }
  
  object NodePointerFactory extends NodePointerFactory {
    def createNodePointer(node: Node) = new Pointer[Node](node) {
      override def hashCode(): int = node.hashCode
    }
    def createParentNodes(root: Node): ParentMap = iCreateParentNodes( root, NodePointerFactory)
  }
  
  case class ParentMap(factory: NodePointerFactory, map: Map[NodePointer, Elem]) {
    def apply(node: Node): Elem = map( factory.createNodePointer(node))
    def apply(node: NodePointer): Elem = map(node)
    def get(node: NodePointer): Option[Elem] = map.get(node)
    def get(node: Node): Option[Elem] = map.get( factory.createNodePointer(node) )
  }

  def createParentNodes(pnode: Node, parentNode: Elem, map: ParentMap ): ParentMap = {
    var imap = map
    // go over each child and add its parent nodes
    for { child <- pnode.child } // can't call the line after unless it is an elem
      imap = createParentNodes(child, pnode.asInstanceOf[Elem], ParentMap(imap.factory, imap.map + (map.factory.createNodePointer(child) -> pnode.asInstanceOf[Elem])) ) 
    imap
  }
  
  def root(node: Node)(implicit map: ParentMap): Option[Node] = {
    val p = map.get(node)
    if (p.isEmpty) Some(node)
    else root( p.get )
  }
  
  case class QName(namespace: Option[String], localName: Some[String], prefix: Option[String])
  
  object QName {
    val * = QName(None, Some(""), None)
    
    def apply(localName: String): QName = QName(None, Some(localName), None)
    def apply(namespace: String, localName: String): QName = QName(Some(namespace), Some(localName), None)
  }
  
  // Just to explain the Projection, its just a nice wrapper to get parent's into for comprehensions
  // should the \^ and \:\ axis return a projection instead of node?
  case class Projection(node: Node, parent: ParentMap) extends Iterable[Node] {
        
    def apply() : Option[Node] = Some(node)
    
    def elements = node.child.elements
    
    /*
    def apply(f: Node => Boolean): NodeSeq = node.filter(f)
    def apply(i : int): NodeSeq = node(i)
    */
    
    def \^ : Projection = {
      val res = root(node)(parent)
      if (res.isDefined)
        Projection(res.get, parent)
      else EmptyProjection
    } 
    
    // gets positional child. This doesn't seem to work with Node
    def \*(i: int) : Iterable[Node] = node.child(i)
    
    // only elements returned.
    def \*(f: Node => Boolean): Iterable[Node] = node.child.filter(x => x.isInstanceOf[Elem] && f(x))
    
    // any child which matches, only elements
    def \\*(f: Node => Boolean): Iterable[Node] = node.descendant_or_self.filter(x => x.isInstanceOf[Elem] && f(x) )
    
    def \(qname: QName ) : Iterable[Node] =
      if (qname eq QName.*)
        this
      else if (qname.namespace.isDefined)
        node.child.filter( x => x.label == qname.localName.get && x.namespace == qname.namespace)
      else node.child.filter( x => x.label == qname.localName.get )
    
    def \:\ : Projection = {
      val res = parent.map.get( parent.factory.createNodePointer(node) )
      if (res.isDefined) 
        Projection( res.get , parent)
      else EmptyProjection
    }    
  }
  
  object EmptyProjection extends Projection(null, null) {
    
    override def apply() : Option[Node] = None
    
    override def elements = Iterator.empty
    
    /*
    def apply(f: Node => Boolean): NodeSeq = node.filter(f)
    def apply(i : int): NodeSeq = node(i)
    */
    
    override def \^ : Projection = this
    
    // gets positional child. This doesn't seem to work with Node
    override def \*( i: int ) : Iterable[Node] = this
    
    // only elements returned.
    override def \*( f: Node => Boolean): Iterable[Node] = this
    
    // any child which matches, only elements
    override def \\*( f: Node => Boolean): Iterable[Node] = this
    
    override def \( qname : QName ) : Iterable[Node] = this
    
    override def \:\ : Projection = this
  }
    
  def main(args : Array[String]) : Unit = {
    var xml = 
      <root>
        <person>
          <name>fred</name>
          <addr>addr1</addr>
        </person>
        <person>
          <name>james</name>
          <addr>addr2</addr>
        </person>
        <bills>
          <bill id="bill1">
            <addr>addr1</addr>
          </bill>
        </bills>
      </root>

    // primitive conversion to scalaxx.xml
    val xxml = scalaxx.xml.Utility.trim(ConstructingParser.fromSource(Source.fromString(xml.toString), true).document.docElem)
    
    val factory = NodePointerFactory
    implicit val map = factory.createParentNodes(xxml)
    
    for
    {
      name <- xxml \ "person" \ "name"
      pnode <- Some(map( name ))
    }
      println(name +"'s parent's parent is " + pnode)
      
    //notice that root(name) just works.  If it had no root in the map then it returns None
      
    for
    {
      name <- xxml \ "person" \ "name"
      root <- root(name)
    }
      println(name +"'s root is " + root)
      
    for
    {
      name <- xxml \\ "person" 
      root <- Projection(name, map) \^()
    }
      println(name +" has root " + root)
    
    for
    {
      name <- xxml \\ "person" 
      child <- Projection(name, map) \* 0
    }
      println(name +" has a first child " + child)
    
    for
    {
      child <- Projection(xxml, map) \\* {_.text == "james"}
    }
      println(" has child " + child)
    
    for
    {
      child <- Projection(xxml, map) \\* {_.text == "james"} 
      parent <- Projection( child, map) \:\()
    }
      println("james parent " + parent)
      
    for
    {
      child <- Projection(xxml, map) \\* {_.text == "james"} 
      addr <- ( Projection( child, map) \:\ ) \ QName("addr")
    }
      println("james address " + addr)
  }
}
